package com.hksj.common.log.aop;

import com.alibaba.fastjson.JSON;
import com.hksj.common.config.utils.HttpUtils;
import com.hksj.common.config.utils.RequestContextHolderUtils;

import com.hksj.common.log.config.LogProperties;
import com.hksj.common.log.entity.OperationRecordLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import com.hksj.common.log.service.OperationRecordLogService;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author zengke
 * @version 1.0
 * @see OperationLogAspect
 * @since 2024/3/15 9:40
 */
@Aspect
@Component
@Slf4j
public class OperationLogAspect {

    @Resource
    private OperationRecordLogService operationRecordLogService;

    @Resource
    private LogProperties logProperties;

    //线程池
    private final ExecutorService executorPool= Executors.newFixedThreadPool(10);

    /**
     * 配置织入点
     * 需要在哪些方法上加强
     *  1、execution(): 表达式主体。
     *  2、第一个*号：表示返回类型，*号表示所有的类型。
     *  3、包名：表示需要拦截的包名，后面的两个句点表示当前包和当前包的所有子包，com.sample.service.impl包、子孙包下所有类的方法。
     *  4、第三个*号：表示类名，*号表示所有的类。
     *  5、*(..):最后这个星号表示方法名，*号表示所有的方法，后面括弧里面表示方法的参数，两个句点表示任何参数
     */
    @Pointcut("execution(* com.hksj.*.controller..*.*(..))")
    public void logPointCut(){
    }

    /**
     * 处理完请求后执行处理日志方法
     * @param joinPoint 切点
     * @param result 返回值
     */
    @AfterReturning(pointcut = "logPointCut()",returning = "result" )
    public void doAfterReturning(JoinPoint joinPoint, Object result){
        //判断是否开启操作日志记录
        if (logProperties.getEnableOperationLog()){
            handleLog(joinPoint,null,result);
        }
    }

    /**
     * 处理操作日志 在接口抛出异常时
     * @param joinPoint 切点
     * @param e 抛出的异常
     */
    @AfterThrowing(value = "logPointCut()",throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint,Exception e){

        //判断是否开启操作日志记录
        if (logProperties.getEnableOperationLog()){
            handleLog(joinPoint,e,null);
        }
    }

    private void handleLog(final JoinPoint joinPoint, final Exception e, Object result){
        try {
            //当前请求者的ip
            String ipAddress = HttpUtils.getIpAddr(RequestContextHolderUtils.getRequest());

            //当前接口请求地址
            String requestUrl = HttpUtils.getRequestUrl();

            //调用的方法
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            String callerMethod = className + "." + methodName + "()";

            //请求方式 POST、GET、PUT.....
            String requestMethod = HttpUtils.getRequestMethod();

            //通过ip查询操作地点
            String location = HttpUtils.getAddressLocation(ipAddress);

            //获取请求的参数
            String requestParam = getRequestParam(joinPoint, requestMethod);

            //用户id
            Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            Long userId = null;
            if (principal instanceof LoginUser){
                userId = ((LoginUser) principal).getUser().getId();
            }

            //构建操作日志对象
            OperationRecordLog operationRecord = OperationRecordLog.builder()
                    .ipAddress(ipAddress)
                    .requestUrl(requestUrl)
                    .callerMethod(callerMethod)
                    .requestMethod(requestMethod)
                    .requestLocation(location)
                    .requestParam(requestParam)
                    .result(String.valueOf(result))
                    .status(e == null ? 0 : 1)
                    .errorMessage(e == null ? null : e.getMessage())
                    .userId(userId)
                    .createTime(new Date())
                    .build();

            //若请求地址包含登录 则不进行日志操作记录
            if (requestUrl.toLowerCase().contains("login")){
                return;
            }
            //异步存储操作日志
            CompletableFuture.runAsync(()->{
                operationRecordLogService.createOperationRecord(operationRecord);
            },executorPool);

        }catch (Exception exp){
            //打印日常信息
            log.error("异常信息:{}",exp.getMessage());
            exp.printStackTrace();
        }
    }

    /**
     * 获取当前请求的参数
     * @param joinPoint 切入点
     * @return 请求参数的json字符串
     */
    private String getRequestParam(JoinPoint joinPoint,String requestMethod){
        String params = "";
        //请求为 PUT、POST时是json对象
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)
                                                        || HttpMethod.PATCH.name().equals(requestMethod)){
            params = argsArrayToString(joinPoint.getArgs());
        } else {
            //请求为GET, HEAD,  DELETE, OPTIONS, TRACE;
            Map<?, ?> paramsMap = (Map<?, ?>) RequestContextHolderUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            params = String.valueOf(paramsMap);
        }
        return params;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (ObjectUtils.isEmpty(paramsArray)) {
            return params.toString().trim();
        }
        for (Object o : paramsArray) {
            if (!isFilterObject(o)) {
                Object jsonObj = JSON.toJSON(o);
                params.append(jsonObj.toString()).append(" ");
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。过滤文件对象、HttpServletRequest、HttpServletResponse对象
     * @param o 对象信息
     * @return 返回结果 true需要过滤 false不需要
     */
    public boolean isFilterObject(final Object o) {
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }

}
